using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using MacomberMap.Common.Internals;
using MacomberMap.Common.Components;
using System.Xml;

namespace MacomberMap.Common.Database_Interface.CIM.Components
{
    /// <summary>
    /// This class handles the information, functions and properties for each CIM element
    /// </summary>
    public class CIM_Element
    {

        #region Variable declarations
        /// <summary>Whether to parse resource nodes from connectivity nodes</summary>
        public static bool ResourceNodesFromConnectivityNodes = false;

        /// <summary>The byte signifying the type of the element</summary>
        public Byte TypeByte;

        /// <summary>The starting position of the element within the CIM file</summary>
        public Int64 Start;

        /// <summary>The length of the element (in bytes) within the CIM file</summary>
        public int Length;

        /// <summary>The position of the element within the CIM array</summary>
        public int Index;

        /// <summary>The collection of incoming links</summary>
        public List<int> _InLinks = new List<int>(10);

        /// <summary>The collection of outgoing links</summary>
        public List<int> _OutLinks = new List<int>(2);

        /// <summary>The rdf:ID of the element</summary>
        public MM_rdfID rdfID;

        /// <summary>The checksum for this element</summary>
        public byte[] CheckSum = new byte[0];
        #endregion

        /// <summary>
        /// The easy to read element type
        /// </summary>
        public String ElemType
        {
            get { return this.Type.Split(':')[1]; }
        }

        /// <summary>
        /// The long name in the model
        /// </summary>
        public string LongName
        {
            get
            {
                Dictionary<String, String> Attr = Attributes;
                String OutName;
                if (!Attributes.TryGetValue("cim:IdentifiedObject.description", out OutName) || String.IsNullOrEmpty(OutName))
                    Attributes.TryGetValue("cim:IdentifiedObject.name", out OutName);
                return OutName;
            }
        }
        /// <summary>
        /// Handle the ToString function
        /// </summary>
        /// <returns>rdf:ID and type</returns>
        public override string ToString()
        {
            //String TEID = this.TEID;
            //return rdfID.ToString() + " (" + Type + ";" + Name + (String.IsNullOrEmpty(TEID) ? ")" : ";" + TEID + ")") ;
            if (this.Type == "cim:ACLineSegment" || this.Type == "cim:SeriesCompensator")
            {
                CIM_Element[] Segs = ACSegmentStations;
                CIM_Element ParentLine = Parse("cim:Line");
                if (Segs[0].Equals(Segs[1]))
                    return this.Type.Split(':')[1] + " " + MM_Repository.TitleCase(ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Name));
                else
                    return this.Type.Split(':')[1] + " " + MM_Repository.TitleCase(ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Name + " (" + Segs[0].Name + " - " + Segs[1].Name + ")"));
            }

            CIM_Element Sub = Substation;
            //CIM_Element Voltage = VoltageLevel;
            //(Voltage == null ? "" : Voltage.Name + " ") + TypeName.Substring(TypeName.IndexOf(':') + 1) + " " + 
            return this.Type.Split(':')[1] + " " + (Sub == null || Sub == this ? "" : Sub.Name + ".") + this.Name;
       
        }


        /// <summary>
        /// Set or get an element's type. When setting, if the
        /// type isn't found, add the type to the master list of MM_Database_CIM.Types.
        /// </summary>
        public String Type
        {
            get { return MM_Database_CIM.Types.GetKeyAt((int)TypeByte); }
            set
            {

                int FoundType;
                lock (MM_Database_CIM.Types)
                    if (MM_Database_CIM.Types.TryGetValue(value, out FoundType))
                        TypeByte = (Byte)FoundType;
                    else
                    {
                        TypeByte = (byte)MM_Database_CIM.Types.Add(value);                       
                        MM_Database_CIM.AttributesByType.Add(TypeByte, new List<ushort>(10));
                    }
            }
        }

        /// <summary>
        /// Convert XML to attributes
        /// </summary>
        /// <param name="InnerXml"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public static Dictionary<String, String> ConvertXmlToAttributes(String InnerXml, String TypeName)
        {
            Dictionary<String, String> OutDic = new Dictionary<string, string>();
            String LastName = "";
            XmlNodeType LastType = XmlNodeType.EndElement;
            using (StringReader sRd = new StringReader(InnerXml))
            using (XmlTextReader xRd = new XmlTextReader(sRd))
            {
                xRd.Namespaces = false;
                xRd.WhitespaceHandling = WhitespaceHandling.None;
                while (xRd.Read())
                {
                    if (String.IsNullOrEmpty(LastName) && xRd.NodeType == XmlNodeType.Element)
                        AddAttribute(OutDic, "Type", xRd.Name);
                    if (xRd.NodeType == XmlNodeType.Element)
                        LastName = xRd.Name;
                    else if (xRd.NodeType == XmlNodeType.Text)
                        AddAttribute(OutDic, LastName, xRd.Value);
                    else if (xRd.NodeType == XmlNodeType.EndElement && LastType == XmlNodeType.Element)
                        AddAttribute(OutDic, LastName, "");
                    for (int a = 0; a < xRd.AttributeCount; a++)
                    {
                        xRd.MoveToAttribute(a);
                        if (xRd.Name == "rdf:resource")
                            AddAttribute(OutDic, LastName, xRd.Value);
                    }
                }
                LastType = xRd.NodeType;
            }
            return OutDic;
        }

        /// <summary>
        /// Add an attribute to our dictionary, and update to support any collisions
        /// </summary>
        /// <param name="AttributeDictionary"></param>
        /// <param name="Name"></param>
        /// <param name="Value"></param>
        private static void AddAttribute(Dictionary<String, String> AttributeDictionary, String Name, String Value)
        {
            if (AttributeDictionary.ContainsKey(Name))
            {
                int a = 1;
                while (AttributeDictionary.ContainsKey(Name + " (" + a + ")"))
                    a++;
                AttributeDictionary.Add(Name + " (" + a + ")", Value);
            }
            else
                AttributeDictionary.Add(Name, Value);
        }

        /// <summary>
        /// Retrieve the attributes for the element
        /// </summary>
        public Dictionary<String, String> Attributes        
        {
            get
            {
                Dictionary<String, String> OutDic =  ConvertXmlToAttributes(this.XML, this.Type); //new Dictionary<string, string>();
                return OutDic;
                
                
                String InnerXml = this.XML;
                OutDic.Add("Type", InnerXml.Substring(1, InnerXml.IndexOf(' ') - 1));
                int CurPos = 1;
                while ((CurPos = InnerXml.IndexOf('<', CurPos) + 1) != 0)
                {
                    String AttrName = InnerXml.Substring(CurPos, InnerXml.IndexOf('>', CurPos) - CurPos);
                    if (AttrName.Contains(" "))
                    {
                        String Val = AttrName.Substring(AttrName.IndexOf('=') + 2);
                        Val = Val.Substring(0, Val.Length - 2);
                        AttrName = AttrName.Substring(0, AttrName.IndexOf(' '));
                        if (!OutDic.ContainsKey(AttrName))
                            OutDic.Add(AttrName, Val);
                        else
                        {
                            int TryNum = 1;
                            while (OutDic.ContainsKey(AttrName + TryNum.ToString()))
                                TryNum++;
                            OutDic.Add(AttrName + TryNum.ToString(), Val);
                        }

                    }
                    else if (AttrName[0] != '/')
                    {
                        String Val = InnerXml.Substring(InnerXml.IndexOf('>', CurPos) + 1);
                        Val = Val.Substring(0, Val.IndexOf('<'));
                        if (OutDic.ContainsKey(AttrName))
                        {
                            int a = 1;
                            while (OutDic.ContainsKey(AttrName + " (" + a + ")"))
                                a++;
                            OutDic.Add(AttrName + " (" + a + ")", Val);
                        }
                        else
                            OutDic.Add(AttrName, Val);
                    }
                }
                //Also, add in the attribute for type
                return OutDic;
            }
        }

        /// <summary>
        /// Retrieve the XML for this element
        /// </summary>
        public String XML
        {
            get
            {
                if (Start == -1)
                    return "<UNKNOWN rdf:ID=\"" + this.rdfID.ToString() + "\">\n</UNKNOWN>" ;
                FileStream fS = new FileStream(MM_Database_CIM.CIMFileName, FileMode.Open, FileAccess.Read, FileShare.Read, this.Length);
                fS.Seek(this.Start, SeekOrigin.Begin);
                Byte[] inB = new byte[this.Length];
                fS.Read(inB, 0, inB.Length);
                fS.Close();
                fS.Dispose();
                return Encoding.UTF8.GetString(inB);

            }
        }

        /// <summary>
        /// If the current element is a Connectivity node, go through its terminals
        /// to find the elements on the other side. 
        /// Also, if it's a power transformer, return the transformer windings on each side.
        /// Also, if it's a substation, return all elements within the substation.
        /// </summary>
        public CIM_Element[] Elements
        {
            get
            {
                List<CIM_Element> NodeElements = new List<CIM_Element>(2);
                if (this.Type == "etx:PricingVector")
                {
                    foreach (CIM_Element Term in this.Links)
                        if (Term.Type == "cim:Terminal")
                            foreach (CIM_Element OutElem in Term.OutLinks)
                                NodeElements.Add(OutElem);
                }
                else if (this.Type == "cim:PowerTransformer")
                {
                    foreach (CIM_Element Winding in this.InLinks)
                        if (Winding.Type == "cim:TransformerWinding")
                            NodeElements.Add(Winding);
                }
                else if (this.Type == "cim:BusbarSection")
                    return this.Parse("cim:Terminal>cim:ConnectivityNode").Elements;
                else if (this.Type == "cim:ConnectivityNode")
                {
                    foreach (CIM_Element term in this.InLinks)
                        if (term.Type == "cim:Terminal")
                        {
                            foreach (CIM_Element otherElem in term.InLinks)
                                if (otherElem.Type == "etx:PricingVector")
                                    NodeElements.Add(otherElem);
                            foreach (CIM_Element otherElem in term.OutLinks)
                                if ((otherElem != this) && (!NodeElements.Contains(otherElem)))
                                    NodeElements.Add(otherElem);
                        }
                        else if (CIM_Element.ResourceNodesFromConnectivityNodes &&  term.Type == "etx:ElectricalBus")
                            foreach (CIM_Element ResourceNode in term.InLinks)
                                if (ResourceNode.Type == "etx:ResourceNode")
                                    NodeElements.Add(ResourceNode);

                }
                else if (this.Type == "cim:Contingency")
                {
                    foreach (CIM_Element ContingencyElement in this.InLinks)
                        if (ContingencyElement.Type == "etx:ContingencyElement")
                            foreach (CIM_Element OtherSide in ContingencyElement.OutLinks)
                                if (OtherSide.Type != "cim:Contingency")                                  
                                        NodeElements.Add(OtherSide);
                }
                else if (this.Type == "cim:Substation")
                {

                    foreach (CIM_Element VoltageLevel in this.InLinks)
                        if (VoltageLevel.Type == "cim:PowerTransformer" && !NodeElements.Contains(VoltageLevel))
                            NodeElements.Add(VoltageLevel);
                        else if (VoltageLevel.Type == "cim:VoltageLevel")
                            foreach (CIM_Element Elem in VoltageLevel.InLinks)
                                if (!NodeElements.Contains(Elem))
                                {
                                    //If we have a connectivity node, make sure it's not included w/ a busbar, and also check 
                                    //to see whether it's attached to a line
                                    if (Elem.Type == "cim:ConnectivityNode")
                                    {
                                        foreach (CIM_Element Elem2 in Elem.Elements)
                                            if (!NodeElements.Contains(Elem2))
                                                NodeElements.Add(Elem2);
                                        CIM_Element BusBar = Elem.Parse("cim:Terminal>cim:BusbarSection");
                                        if (BusBar != null && !NodeElements.Contains(BusBar))
                                            NodeElements.Add(BusBar);
                                        NodeElements.Add(Elem);
                                    }
                                    else
                                        NodeElements.Add(Elem);
                                }
                }
                else
                { }
                return NodeElements.ToArray();
            }
        }

        /// <summary>
        /// If the current element is an Element (not a connectivity node, but connected to terminals),
        /// go through its terminals to find elements on the other side.
        /// </summary>
        public CIM_Element[] Nodes
        {
            get
            {
                //Now go from terminal -> next
                List<CIM_Element> NodeElements = new List<CIM_Element>(8);

                if (this.Type == "cim:ConnectivityNode")
                { }
                else if (this.Type == "cim:Line")
                {
                    foreach (CIM_Element LineSeg in this.Links)
                        if (LineSeg.Type == "cim:ACLineSegment" || LineSeg.Type == "cim:SeriesCompensator")
                            return LineSeg.Nodes;
                }
                else if (this.Type == "cim:Substation")
                {
                    foreach (CIM_Element KVLevel in this.InLinks)
                        if (KVLevel.Type == "cim:VoltageLevel")
                            foreach (CIM_Element Elem in KVLevel.InLinks)
                                if (Elem.Type == "cim:ConnectivityNode" && !NodeElements.Contains(Elem))
                                    NodeElements.Add(Elem);
                }
                else if (this.Type == "cim:PowerTransformer")
                {
                    foreach (CIM_Element TransformerWinding in this.Links)
                        if (TransformerWinding.Type == "cim:TransformerWinding")
                            foreach (CIM_Element Node in TransformerWinding.Nodes)
                                if (Node != this)
                                    NodeElements.Add(Node);
                }
                else
                {
                    foreach (CIM_Element term in this.InLinks)
                        if (term.Type == "cim:Terminal")
                            foreach (CIM_Element otherElem in term.OutLinks)
                                if ((otherElem != this) && (!NodeElements.Contains(otherElem)))
                                    NodeElements.Add(otherElem);
                    foreach (CIM_Element term in this.OutLinks)
                        if ((this.Type == "cim:TransformerWinding") && (term.Type == "cim:PowerTransformer"))
                            NodeElements.Add(term);
                }
                return NodeElements.ToArray();
            }
        }

        /// <summary>
        /// Retrieve the TEID of the element, if any
        /// </summary>
        public String TEID
        {
            get
            {
                if (Length == -1) return "";
                String ThisXML = this.XML;
                int TEIDPos = this.XML.IndexOf(".TEID>", StringComparison.CurrentCultureIgnoreCase);
                if (TEIDPos > 0)
                    return ThisXML.Substring(TEIDPos + 6, ThisXML.IndexOf("<", TEIDPos + 7) - TEIDPos - 6);
                else
                    return String.Empty;

            }
        }



        /// <summary>
        /// Return the unit attached to a synchronous machine
        /// </summary>
        public CIM_Element SynchronousToUnit
        {
            get
            {
                if (this.Type != "cim:SynchronousMachine")
                    throw new InvalidDataException("Cannot locate a unit for this type.");
                foreach (CIM_Element OutLnk in this.OutLinks)
                    switch (OutLnk.Type)
                    {
                        case "cim:ThermalGeneratingUnit":
                        case "etx:WindGeneratingUnit":
                        case "cim:HydroGeneratingUnit":
                        case "etx:NuclearGeneratingUnit":
                            return OutLnk;
                    }
                return null;
            }
        }



        /// <summary>
        /// Return the substations around the current element. Particularly useful
        /// for an AC Line Segment, as it will return the stations connected to the line.
        /// </summary>
        public CIM_Element[] ACSegmentStations
        {
            get
            {                
                if (this.Type == "cim:ACLineSegment" || this.Type == "cim:SeriesCompensator")
                {
                    List<CIM_Element> Out = new List<CIM_Element>(5);
                    foreach (CIM_Element Term in this.InLinks)
                        if (Term.Type == "cim:Terminal")
                            foreach (CIM_Element Node in Term.OutLinks)
                                if (Node.Type == "cim:ConnectivityNode")
                                    Out.Add(Node.Substation);
                    return Out.ToArray();
                }                 
                 
                
                else if (this.Type == "cim:Line")
                {
                    foreach (CIM_Element LineSeg in this.InLinks)
                        if (LineSeg.Type == "cim:ACLineSegment" || LineSeg.Type == "cim:SeriesCompensator")
                            return LineSeg.ACSegmentStations;
                    throw new InvalidOperationException("Line stations can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");
                }
                else
                    throw new InvalidOperationException("Line stations can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");
                
            }
        }

        /// <summary>
        /// Return the appropriate unit for a Synchronous Machine or DC tie
        /// </summary>
        public CIM_Element Unit
        {
            get
            {

                if (this.Type != "cim:SynchronousMachine" && this.Type != "etx:DCTie")
                    throw new InvalidDataException("You can only retrieve a unit for a synchronous machine or DC tie!");
                foreach (CIM_Element Elem in this.Links)
                    switch (Elem.Type.Split(':')[1])
                    {
                        case "HydroGeneratingUnit":
                        case "ThermalGeneratingUnit":
                        case "NuclearGeneratingUnit":
                        case "WindGeneratingUnit":
                            return Elem;
                    }
                return null;
            }

        }


        /// <summary>
        /// Return a list of elements that are connected to the current element
        /// </summary>
        public CIM_Element[] Links
        {
            get
            {
                List<CIM_Element> Out = new List<CIM_Element>(this._InLinks.Count + this._InLinks.Count);
                foreach (int i in this._InLinks)
                    Out.Add(MM_Database_CIM.Elements[i]);
                foreach (int i in this._OutLinks)
                    Out.Add(MM_Database_CIM.Elements[i]);
                return Out.ToArray();
            }
        }

        /// <summary>
        /// Return a list of elements that refer to the current element
        /// </summary>
        public CIM_Element[] InLinks
        {
            get
            {
                List<CIM_Element> Out = new List<CIM_Element>(this._InLinks.Count + this._InLinks.Count);
                foreach (int i in this._InLinks)
                    Out.Add(MM_Database_CIM.Elements[i]);
                return Out.ToArray();
            }
        }

        /// /// <summary>
        /// Return a list of elements that the current element refers to
        /// </summary>
        public CIM_Element[] OutLinks
        {
            get
            {
                try
                {
                    List<CIM_Element> Out = new List<CIM_Element>(this._InLinks.Count + this._InLinks.Count);
                    foreach (int i in this._OutLinks)
                        Out.Add(MM_Database_CIM.Elements[i]);
                    return Out.ToArray();
                }
                catch (OutOfMemoryException)
                {
                    GC.Collect();
                    for (int a = 0; a < 10; a++)
                        System.Windows.Forms.Application.DoEvents();
                    return OutLinks;
                }
            }
        }

        /// <summary>
        /// Return the company for the current element, if possible.
        /// </summary>
        public CIM_Element Company
        {
            get
            {
                if (this.Type == "cim:Company")
                    return this;

                //First try looking for a direct company
                foreach (CIM_Element Elem in this.Links)
                    if (Elem.Type == "etx:Ownership")
                        foreach (CIM_Element Elem2 in Elem.Links)
                            if (Elem2.Type == "cim:Company")
                                return Elem2;

                //If not, try the cim:VoltageLevel
                foreach (CIM_Element Volt in this.Links)
                    if (Volt.Type == "cim:VoltageLevel")
                        foreach (CIM_Element Elem in Volt.Links)
                            if (Elem.Type == "etx:Ownership")
                                foreach (CIM_Element Elem2 in Elem.Links)
                                    if (Elem2.Type == "cim:Company")
                                        return Elem2;

                return null;
            }
        }



        /// <summary>
        /// Return the voltage level for the current element, if possible.
        /// </summary>
        public CIM_Element VoltageLevel
        {
            get
            {
                if (this.Type.EndsWith("Unit"))
                {
                    foreach (CIM_Element Synch in this.InLinks)
                        if (Synch.Type == "cim:SynchronousMachine")
                            return Synch.VoltageLevel;
                }
                else if (this.Type == "cim:TransformerWinding")
                {
                    foreach (CIM_Element Volt in this.OutLinks)
                        if (Volt.Type == "cim:BaseVoltage")
                            return Volt;
                }
                else if (this.Type == "etx:EPSMeter")                
                    return this.Parse("etx:PricingVector").VoltageLevel;
                else if (this.Type == "etx:PricingVector")
                    return this.Parse("cim:Terminal>cim:ConnectivityNode").VoltageLevel;                
                else if (this.Type == "cim:Line")
                {
                    CIM_Element ACSeg = this.Parse("cim:ACLineSegment>cim:BaseVoltage");
                    if (ACSeg != null)
                        return ACSeg;
                    else
                        return this.Parse("cim:SeriesCompensator>cim:BaseVoltage");
                }
                else
                    foreach (CIM_Element Volt in this.OutLinks)
                        if (Volt.Type == "cim:Bay")
                            return Volt.VoltageLevel;
                        else if (Volt.Type == "cim:VoltageLevel")
                        {
                            foreach (CIM_Element BV in Volt.OutLinks)
                                if (BV.Type == "cim:BaseVoltage")
                                    return BV;
                        }
                foreach (CIM_Element Volt in this.OutLinks)
                    if (Volt.Type == "cim:BaseVoltage")
                        return Volt;
                return null;
            }
        }

        /// <summary>
        /// When possible, return the owner of the item in question
        /// </summary>
        public CIM_Element Owner
        {
            get
            {
                if (this.Type.EndsWith("Unit"))
                {
                    foreach (CIM_Element Synch in InLinks)
                        if (Synch.Type == "cim:SynchronousMachine")
                            return Synch.Owner;
                }
                else if (this.Type == "etx:EPSMeter")
                    return this.Parse("etx:PricingVector").Owner;
                else if (this.Type == "etx:PricingVector")
                    return this.Parse("etx:ElectricalBus>cim:ConnectivityNode").Owner;
                else
                {
                    foreach (CIM_Element OwnFind in OutLinks)
                        if (OwnFind.Type == "cim:VoltageLevel")
                            return OwnFind.Owner;
                        else if (OwnFind.Type == "etx:Ownership")
                            foreach (CIM_Element Company in OwnFind.OutLinks)
                                if (Company.Type == "cim:Company")
                                    return Company;
                }

                //If we can't find an owner, try substation
                CIM_Element Sub = Substation;
                if (Sub != null)
                    return Sub.Owner;
                else
                    return null;
            }
        }

        /// <summary>
        /// When possible, return the operator of the item in question
        /// </summary>
        public CIM_Element Operator
        {
            get
            {
                if (this.Type.EndsWith("Unit"))
                {
                    foreach (CIM_Element Synch in InLinks)
                        if (Synch.Type == "cim:SynchronousMachine")
                            return Synch.Operator;
                }
                else if (this.Type == "etx:EPSMeter")
                    return this.Parse("etx:PricingVector").Operator;
                else if (this.Type == "etx:PricingVector")
                    return this.Parse("etx:ElectricalBus>cim:ConnectivityNode").Operator;
                else
                {
                    foreach (CIM_Element OwnFind in OutLinks)
                        if (OwnFind.Type == "cim:VoltageLevel")
                            return OwnFind.Operator;
                        else if (OwnFind.Type == "etx:Operatorship")
                            foreach (CIM_Element Company in OwnFind.OutLinks)
                                if (Company.Type == "cim:Company")
                                    return Company;
                }
                return null;
            }
        }

        /// <summary>
        /// When possible, return the substation for the current element.
        /// </summary>
        public CIM_Element Substation
        {
            get
            {
                if (this.Type == "cim:Substation")
                    return this;
                else if (this.Type == "cim:CombinedCyclePlant")
                    return Parse("etx:ResourceController").Substation;
                else if (this.Type == "etx:EPSMeter")
                    return Parse("etx:PricingVector>cim:Terminal").Substation;
                else if (this.Type == "etx:PricingVector")
                    return Parse("cim:Terminal").Substation;
                else if (this.Type == "cim:TransformerWinding")
                {
                    foreach (CIM_Element XF in this.OutLinks)
                        if (XF.Type == "cim:PowerTransformer")
                            foreach (CIM_Element Substn in XF.OutLinks)
                                if (Substn.Type == "cim:Substation")
                                    return Substn;
                                else if (Substn.Type == "cim:VoltageLevel")
                                    return Substn.Substation;
                }
                else if (this.Type == "etx:DCTie")
                    return this.Unit.Substation;
                else if ((this.Type == "etx:ElectricalBus") || (this.Type == "cim:Terminal"))
                {
                    foreach (CIM_Element Node in this.OutLinks)
                        if (Node.Type == "cim:ConnectivityNode")
                            return Node.Substation;
                }
                else if (this.Type == "etx:ResourceNode")
                {
                    foreach (CIM_Element EB in this.OutLinks)
                        if (EB.Type == "etx:ElectricalBus")
                            return EB.Substation;
                }
                else if (this.Type.EndsWith("Unit"))
                    foreach (CIM_Element Elem in this.Links)
                        if (Elem.Type == "cim:SynchronousMachine")
                            return Elem.Substation;

                foreach (CIM_Element VoltageLevel in OutLinks)
                    if (VoltageLevel.Type == "cim:Bay")
                        return VoltageLevel.Substation;
                    else if (VoltageLevel.Type == "cim:Substation")
                        return VoltageLevel;
                    else if (VoltageLevel.Type == "cim:VoltageLevel")
                        foreach (CIM_Element Substn in VoltageLevel.OutLinks)
                            if (Substn.Type == "cim:Substation")
                                return Substn;
                return null;
            }
        }

        /// <summary>
        /// Determine if an element is connected to a contingency definition
        /// </summary>
        public CIM_Element Contingency
        {
            get
            {
                foreach (CIM_Element CtgElem in this.InLinks)
                    if (CtgElem.Type == "etx:ContingencyElement")
                        foreach (CIM_Element Ctg in this.OutLinks)
                            if (Ctg.Type == "cim:Contingency")
                                return Ctg;
                return null;
            }
        }

        /// <summary>
        /// Return the list of substations around a particular element. If it's a substation, report the 
        /// substations connected to this one via line segments. If it's a line or line segment, report the 
        /// substations around that line. If it's another element, report a single substation.
        /// </summary>
        public CIM_Element[] Substations
        {
            get
            {
                List<CIM_Element> OutList = new List<CIM_Element>(2);
                if (this.Type == "cim:Substation")
                {
                    foreach (CIM_Element VoltageLevel in this.InLinks)
                        if (VoltageLevel.Type == "cim:VoltageLevel")
                            foreach (CIM_Element Node in VoltageLevel.InLinks)
                                if (Node.Type == "cim:ConnectivityNode")
                                    foreach (CIM_Element ACLineSeg in Node.Elements)
                                        if (ACLineSeg.Type == "cim:Line")
                                        {
                                            foreach (CIM_Element Stations in ACLineSeg.Substations)
                                                if (Stations != this)
                                                    OutList.Add(Stations);
                                        }
                                        else if (ACLineSeg.Type == "cim:ACLineSegment" || ACLineSeg.Type == "cim:SeriesCompensator")
                                            foreach (CIM_Element Stations in ACLineSeg.ACSegmentStations)
                                                if (Stations != this)
                                                    OutList.Add(Stations);
                }
                else if (this.Type == "cim:ACLineSegment")
                    OutList.AddRange(this.ACSegmentStations);
                else if (this.Type == "cim:SeriesCompensator")
                    OutList.AddRange(this.ACSegmentStations);
                else if (this.Type == "cim:Contingency")
                {
                    foreach (CIM_Element Elem in this.Elements)
                        if (Elem.Substation != null && !OutList.Contains(Elem.Substation))
                            OutList.Add(Elem.Substation);
                        else if (Elem.Type == "cim:ACLineSegment" || Elem.Type == "cim:SeriesCompensator")
                            foreach (CIM_Element Sub in Elem.ACSegmentStations)
                                if (!OutList.Contains(Sub))
                                    OutList.Add(Sub);
                }
                else if (this.Type == "cim:Line")
                    foreach (CIM_Element Link in this.Links)
                        if (Link.Type == "cim:ACLineSegment" || Link.Type == "cim:SeriesCompensator")
                            OutList.AddRange(Link.ACSegmentStations);
                        else if (Link.Substation != null)
                            OutList.Add(Link.Substation);
                        
                return OutList.ToArray();
            }
        }


        /// <summary>
        /// Retrieve the item's cim:IdentifiedObject.name.
        /// </summary>
        public String Name
        {
            get
            {
                if (Length == -1)
                    return "";
                else
                    return GetXMLValue("cim:IdentifiedObject.name", this.XML);
            }
        }

        /// <summary>
        /// Return the attribute in question, navigating through a chain.
        /// </summary>
        /// <param name="inVal">The chain to progress through. [type]>[type]>[attribute]</param>
        /// <example>this["cim:ConnectivityNode>cim:Terminal>cim:TransformerWinding>cim:IdentifiedObject.name</example> 
        /// <returns>When possible, the string of the navigation chain.</returns>
        public String this[String inVal]
        {
            get
            {
                if (inVal == "") return "";
                String[] toSearch = inVal.Split('>');
                if (toSearch.Length > 1)
                {
                    CIM_Element TargetElement = Parse(String.Join(">", toSearch, 0, toSearch.Length - 1));
                    if (TargetElement == null)
                        return "";
                    else
                        return TargetElement[toSearch[toSearch.Length - 1]];
                }

                else if (toSearch[0][0] == '\"')
                    return toSearch[0].Substring(1);
                switch (toSearch[0].ToLower())
                {
                    case "rdf:id":
                    case "rdfid":
                        return this.rdfID.ToString();
                    case "type":
                        return this.Type;
                    case "name":
                        return this.Name;
                    case "xml":
                        return this.XML;
                    default:
                        return GetXMLValue(inVal, this.XML);
                }
            }
        }


        /// <summary>
        /// Retrieve the value of an element's attribute
        /// </summary>
        /// <param name="Attribute">The attribute to search for</param>
        /// <param name="XML"></param>
        /// <returns></returns>
        public static String GetXMLValue(String Attribute, String XML)
        {
            Dictionary<String, string> Attr = ConvertXmlToAttributes(XML, "");
            String FoundVal;
            if (Attr.TryGetValue(Attribute, out FoundVal))
                return FoundVal;
            else
                return "";

            int TitleStart = Math.Max(XML.IndexOf("<" + Attribute + ">", StringComparison.CurrentCultureIgnoreCase), XML.IndexOf("<" + Attribute + " ", StringComparison.CurrentCultureIgnoreCase));
            if (TitleStart == -1) return "";
            TitleStart += Attribute.Length + 2;
            int CloseXML = XML.IndexOf("</" + Attribute + ">", TitleStart, StringComparison.CurrentCultureIgnoreCase);
            if (CloseXML == -1)
                CloseXML = XML.IndexOf("/>", TitleStart, StringComparison.CurrentCultureIgnoreCase);

            return (XML.Substring(TitleStart, CloseXML - TitleStart)).Replace("&amp;", "&").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&quot;", "\"").Replace("&apos;", "'");
            
        }

        /// <summary>
        /// Retrieve the value of an element's attribute (searching for a partial (e.g., r can be cim:Conductor.r or cim:SeriesCompensator.r)
        /// </summary>
        /// <param name="Attribute">The attribute to search for</param>
        /// <param name="XML"></param>
        /// <returns></returns>
        public static String GetPartialXMLValue(String Attribute, String XML)
        {
            int TitleStart = Math.Max(XML.IndexOf(Attribute + ">"), XML.IndexOf(Attribute + " "));
            if (TitleStart == -1) return "";
            TitleStart += Attribute.Length + 1;
            int CloseXML = XML.IndexOf("</", TitleStart);
            if (CloseXML != -1)
                return XML.Substring(TitleStart, CloseXML - TitleStart);
            else
            {
                CloseXML = XML.IndexOf("/>", TitleStart);
                return XML.Substring(TitleStart, CloseXML - TitleStart);
            }
        }




        /// <summary>
        /// Navigate through CIM topology to retrieve a particular element
        /// </summary>
        /// <param name="LineToParse"></param>
        /// <param name="ParsedElements"></param>
        /// <returns></returns>
        public CIM_Element Parse(String LineToParse, params CIM_Element[] ParsedElements)
        {             
            String[] ToSearch = LineToParse.Split('>');
            String TargetType = ToSearch[0];

            if (TargetType == "ACStations[0]")
                if (ToSearch.Length == 1)
                    return this.ACSegmentStations[0];
                else
                    return this.ACSegmentStations[0].Parse(String.Join(">", ToSearch, 1, ToSearch.Length - 1));
            else if (TargetType == "ACStations[1]")
                if (ToSearch.Length == 1)
                    return this.ACSegmentStations[1];
                else
                    return this.ACSegmentStations[1].Parse(String.Join(">", ToSearch, 1, ToSearch.Length - 1));
            else if (TargetType == "Station")
                if (ToSearch.Length == 1)
                    return this.Substation;
                else
                    return this.Substation.Parse(String.Join(">", ToSearch, 1, ToSearch.Length - 1));
            //else if ((TargetType == "Unit") && (this.Type == "cim:SynchronousMachine"))
             //   return this.Unit;

            int TargetOne = 0, CurrentOne = 1;
            if (TargetType[TargetType.Length - 1] == ']')
            {
                TargetOne = int.Parse(TargetType.Substring(TargetType.LastIndexOf('[') + 1, (TargetType.LastIndexOf(']') - 1 - TargetType.LastIndexOf('['))));
                TargetType = TargetType.Substring(0, TargetType.LastIndexOf('['));
            }


            foreach (CIM_Element Link in this.Links)
                if (Link.Type.EndsWith(TargetType, StringComparison.CurrentCultureIgnoreCase))
                    if (ToSearch.Length == 1)
                        if (((TargetOne == CurrentOne) || (TargetOne == 0)) && (Array.IndexOf(ParsedElements, Link) == -1))
                            return Link;
                        else
                            CurrentOne++;
                    else
                        if ((TargetOne == CurrentOne) && (Array.IndexOf(ParsedElements, Link) == -1))
                            return Link.Parse(String.Join(">", ToSearch, 1, ToSearch.Length - 1));
                        else if (TargetOne != 0)
                            CurrentOne++;
                        else
                        {
                            CIM_Element Test = Link.Parse(String.Join(">", ToSearch, 1, ToSearch.Length - 1));
                            if (Test != null)
                                return Test;
                        }
            return null;
        }

        /// <summary>
        /// For a power transformer, return all connected transformer windings
        /// </summary>
        public CIM_Element[] TransformerWinding
        {
            get
            {
                if (Type != "cim:PowerTransformer")
                    throw new InvalidDataException("Unable to get transformer windings except on power transformers!");
                List<CIM_Element> OutElem = new List<CIM_Element>(_InLinks.Count + _OutLinks.Count);
                foreach (CIM_Element Elem in Links)
                    if (Elem.Type == "cim:TransformerWinding")
                        OutElem.Add(Elem);
                return OutElem.ToArray();
            }
        }

        /*/// <summary>The list of possible elements within a substation as a bitmask.</summary>
        [FlagsAttribute]
        public enum StationElementsEnum { None = 0, Switching = 1, Unit = 2, Load = 4, XF = 8, Capacitor = 16, Reactor = 32, EPSMeter = 64, ElectricalBus = 128, DCTie = 256, SVS = 512, LAAR=1024 };
        */

        /// <summary>
        /// Return a list of all station elements based on their CIM linkages
        /// </summary>
        /// <param name="ElementDictionary"></param>
        /// <returns></returns>
        public Int32 StationElements(Dictionary<String, Int32> ElementDictionary)
        {
            int stationElements = 0;

            //Go through each voltage level, and every element within
            foreach (CIM_Element VoltageLevel in InLinks)
                if (VoltageLevel.Type == "cim:PowerTransformer")
                    stationElements |= ElementDictionary["Transformer"];
                else if (VoltageLevel.Type == "cim:VoltageLevel")
                    foreach (CIM_Element Elem in VoltageLevel.InLinks)
                        switch (Elem.Type)
                        {
                            case "cim:StaticVarCompensator": stationElements |= ElementDictionary["StaticVarCompensator"]; break;
                            case "cim:ShuntCompensator": stationElements |= ElementDictionary["ShuntCompensator"]; break;
                            case "cim:SeriesCompensator": stationElements |= ElementDictionary[""]; break;
                            case "cim:Disconnector": stationElements |= ElementDictionary["Switch"]; break;
                            case "cim:BusbarSection": stationElements |= ElementDictionary["ElectricalBus"]; break;
                            case "cim:SynchronousMachine":
                                if (Elem.Parse("etx:DCTie") != null)
                                    stationElements |= ElementDictionary["DCTie"];
                                else
                                    stationElements |= ElementDictionary["Unit"];
                                //Check through the gen units for an EPS meter.
                                foreach (CIM_Element GenUnit in Elem.OutLinks)
                                    if (GenUnit.Type.EndsWith("GeneratingUnit"))
                                        foreach (CIM_Element EPSMeter in GenUnit.InLinks)
                                            if (EPSMeter.Type == "etx:EPSMeter")
                                                stationElements |= ElementDictionary["EPSMeter"];
                                break;
                            case "cim:CustomerLoad":
                                if (Elem.Parse("etx:DCTie") != null)
                                    stationElements |= ElementDictionary["DCTie"];
                                else if (Elem.Parse("etx:LoadResource") != null)
                                    stationElements |= ElementDictionary["LAAR"];
                                else
                                    stationElements |= ElementDictionary["Load"];
                                break;
                        }

            return stationElements;

        }

        /// <summary>
        /// Go across multiple elements to locate the target one
        /// </summary>
        /// <param name="LineToParse"></param>
        /// <returns></returns>
        public CIM_Element[] ParseMultiples(String LineToParse)
        {
            List<CIM_Element> OutElements = new List<CIM_Element>();
            RecursiveParse(OutElements, LineToParse.Split('>'), 0, new CIM_Element[] { });
            return OutElements.ToArray();
        }

        /// <summary>
        /// Recursively go through all elements, parsing out the touched ones
        /// </summary>
        /// <param name="OutElements"></param>
        /// <param name="LinesToParse"></param>
        /// <param name="CurrentNumber"></param>
        /// <param name="ParsedElements"></param>
        private void RecursiveParse(List<CIM_Element> OutElements, String[] LinesToParse, int CurrentNumber, CIM_Element[] ParsedElements)
        {

            //First, update our outgoing list with the current element
            CIM_Element[] OutElems = new CIM_Element[ParsedElements.Length + 1];
            ParsedElements.CopyTo(OutElems, 0);
            OutElems[OutElems.Length - 1] = this;

            //Now, determine the list of links we should be following, and go through them
            foreach (CIM_Element Link in this.Links)
                if (CurrentNumber == LinesToParse.Length - 1 && Link.Type == LinesToParse[LinesToParse.Length - 1])
                    OutElements.Add(Link);
                else if (CurrentNumber < LinesToParse.Length - 1)
                    Link.RecursiveParse(OutElements, LinesToParse, CurrentNumber++, ParsedElements);
            return;


            //If we're at the end of our lines, return.
            if (CurrentNumber == LinesToParse.Length)
            {
                //OutElements.Add(this);
                return;
            }

            //First, update our list with the current element
            
            //Now, go through all links to find our current number
            foreach (CIM_Element Link in this.Links)
                if (Link.Type.EndsWith(LinesToParse[CurrentNumber], StringComparison.CurrentCultureIgnoreCase) && Array.IndexOf(ParsedElements, this) == -1)
                    Link.RecursiveParse(OutElements, LinesToParse, CurrentNumber + 1, OutElems);                                       

        }

    }
}
